import { Mutable } from "../../common/types";
import { stringIsBoolean, stringIsFloat, stringIsHexNumber, stringIsInteger, stringIsOctalNumber } from "../../common/util";
import { NodeFactory } from "./factory";
import { objectAllocator } from "./impl";
import {
    ArrayDecl,
    Converter,
    Declaration,
    EmptyExpr,
    FalseLiteral,
    Identifier,
    LiteralType,
    MutableNodeArray,
    Node,
    NumberLiteral,
    StringLiteral,
    SyntaxKind,
    Token,
    TrueLiteral,
    VariableDecl
} from "./types";
import {
    stringToDeclareKeyword,
    stringToMrsKeyword
} from "./util";


export function createConverter(factory: NodeFactory): Converter {

    let nodeFactory = factory;
    let nodeConstructor: new (node: Node) => Node;

    return {
        get factory() { return nodeFactory; },
        convertIdentifierToArrayDeclaration,
        convertIdentifierToStringLiteral,
        convertIdentifierToVariableDeclaration,
        convertNodesToStringLiteral,
        convertNodeToDeclaration,
        convertNodeToIdentifier,
        convertNodeToNumberLiteral,
        convertNodeToStringLiteral,
        convertNodeToToken,
        convertNumberLiteralToStringLiteral,
        convertStringLiteralToBooleanLiteral,
        convertStringLiteralToIdentifiers,
        convertStringLiteralToNumberLiteral,
    };

    function copyNode<T extends Node>(node: Node, flag: T["kind"]): Mutable<T> {
        const copy = new (nodeConstructor || (nodeConstructor = objectAllocator.getNodeCopyConstructor()))(node) as Mutable<Node>;
        copy.kind = flag;
        return copy as Mutable<T>;
    }

    function convertNodeToToken<T extends SyntaxKind>(node: Node, flag: T): Token<T> {
        const token = copyNode<Token<T>>(node, flag);
        token.keyword = stringToMrsKeyword(node.text);
        token.delcareKeyword = stringToDeclareKeyword(node.text);
        return token;
    }

    function convertNodeToNumberLiteral(node: Node): NumberLiteral {
        const copy = copyNode<NumberLiteral>(node, SyntaxKind.numberLiteral);
        copy.isFloat = stringIsFloat(node.text);
        copy.value = 0;
        copy.literalType = LiteralType.number;

        if (copy.isFloat) {
            copy.literalType = LiteralType.number;
            copy.value = Number.parseFloat(node.text);
        }
        else {
            let str = node.text.trim();
            if (stringIsHexNumber(str) && str.length > 2) {
                copy.literalType = LiteralType.hexNumber;
                try {
                    copy.value = Number.parseInt(str.substring(2), 16);
                } catch {}
            }
            else if (stringIsOctalNumber(str) && str.length > 2) {
                copy.literalType = LiteralType.octNumber;
                try {
                    copy.value = Number.parseInt(str.substring(2), 8);
                } catch {}
            }
            else if (stringIsInteger(str)) {
                copy.literalType = LiteralType.number;
                try {
                    copy.value = Number.parseInt(str, 10);
                } catch {}
            }
        }

        return copy;
    }

    function convertNodeToStringLiteral(expression: Node): StringLiteral {
        const node = copyNode<StringLiteral>(expression, SyntaxKind.stringLiteral);
        node.literalType = LiteralType.singleLineString;
        return node;
    }

    function convertNodesToStringLiteral(nodes: readonly Node[], separator: string): StringLiteral {
        let start = -1;
        let end = -1;
        let text = nodes.length > 0 ? nodes.map(n => n.text).join(separator) : "";

        for (const n of nodes) {
            if (start === -1 || start > n.start) {
                start = n.start;
            }
            if (end === -1 || end < n.end) {
                end = n.end;
            }
        }

        const literal = nodeFactory.createStringLiteral(text, LiteralType.singleLineString) as Mutable<StringLiteral>;
        literal.start = start;
        literal.end = end;
        return literal;
    }

    function convertNodeToIdentifier(node: Node): Identifier {
        return copyNode<Identifier>(node, SyntaxKind.identifier);
    }

    function convertStringLiteralToIdentifiers(literal: StringLiteral, delimiter: string): Identifier[] {
        const nodes: Identifier[] = [];
        const arr = literal.text.split(delimiter);
        let start = literal.start;
        for (let i = 0; i < arr.length; i++) {
            const element = arr[i];
            const id = copyNode<Identifier>(literal, SyntaxKind.identifier);
            id.text = element.trim();
            id.start = start;
            id.end = start + element.length;
            nodes.push(id);
            start += element.length + delimiter.length;
        }
        return nodes;
    }

    function convertStringLiteralToBooleanLiteral(literal: StringLiteral): TrueLiteral | FalseLiteral {
        let node: Mutable<TrueLiteral | FalseLiteral>;
        if (stringIsBoolean(literal.text)) {
            if (literal.text.trim().toLowerCase() === "true") {
                node = copyNode<TrueLiteral>(literal, SyntaxKind.trueLiteral);
                node.literalType = LiteralType.true;
            }
            else {
                node = copyNode<FalseLiteral>(literal, SyntaxKind.falseLiteral);
                node.literalType = LiteralType.false;
            }
        }
        else {
            node = copyNode<TrueLiteral>(literal, SyntaxKind.trueLiteral);
            node.literalType = LiteralType.true;
        }
        return node;
    }

    function convertIdentifierToStringLiteral(identifier: Identifier): StringLiteral {
        const node = copyNode<StringLiteral>(identifier, SyntaxKind.stringLiteral);
        node.literalType = LiteralType.singleLineString;
        return node;
    }

    function convertNumberLiteralToStringLiteral(literal: NumberLiteral): StringLiteral {
        const node = copyNode<StringLiteral>(literal, SyntaxKind.stringLiteral);
        node.literalType = LiteralType.singleLineString;
        return node;
    }

    function convertStringLiteralToNumberLiteral(literal: StringLiteral, value: number): NumberLiteral {
        const node = copyNode<NumberLiteral>(literal, SyntaxKind.numberLiteral);
        node.literalType = LiteralType.number;
        node.isFloat = !Number.isInteger(value);
        node.value = value;
        return node;
    }

    function convertNodeToDeclaration(node: Node): Declaration {
        const declaration = copyNode<Declaration>(node, SyntaxKind.varDecl);
        declaration.name = copyNode<Identifier>(node, SyntaxKind.identifier);
        return declaration;
    }

    function convertIdentifierToVariableDeclaration(identifier: Identifier): VariableDecl {
        const node = copyNode<VariableDecl>(identifier, SyntaxKind.varDecl);
        node.name = identifier;
        return node;
    }

    function convertIdentifierToArrayDeclaration(identifier: Identifier): ArrayDecl {
        const node = copyNode<ArrayDecl>(identifier, SyntaxKind.arrayDecl);
        const d: (NumberLiteral | EmptyExpr)[] = [];
        const dimensions = d as MutableNodeArray<NumberLiteral | EmptyExpr>;
        dimensions.start = -1;
        dimensions.end = -1;
        node.dimensions = dimensions;
        node.rank = 1;
        node.name = identifier;
        return node;
    }

}
